package org.jeecg.common.util.pay.wx;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.app.AppServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.pay.vo.CcRefundOrderVO;
import org.jeecg.common.util.pay.vo.CcWxPayOrderVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author Van.Planifolia
 * @Date 2024/1/24
 * @Version 1.0
 */
@Getter
@Component
@Slf4j
public class WxPayUtil {

    /**
     * 通知解析器
     */
    @Resource
    private NotificationParser notificationParser;
    /**
     * 支付配置
     */
    @Resource
    private WxPayProperties wxPayProperties;

    /**
     * JSAPI service
     */
    private final JsapiServiceExtension jsApiService;
    /**
     * 退款服务
     */
    private final RefundService refundService;

    /**
     * APP支付服务
     */
    private final AppServiceExtension appServiceExtension;


    /**
     * 初始化微信配置
     */
    @Autowired
    public WxPayUtil(Config wxPayConfig) {
        // 初始化 NotificationParser 回调通知解析器
        this.notificationParser = new NotificationParser((NotificationConfig) wxPayConfig);
        // 初始化jsAPIServer js支付服务
        this.jsApiService = new JsapiServiceExtension.Builder().config(wxPayConfig).build();
        // 初始化退款Server
        this.refundService = new RefundService.Builder().config(wxPayConfig).build();
        // 初始化APP支付Server
        this.appServiceExtension = new AppServiceExtension.Builder().config(wxPayConfig).build();
    }

    /**
     * 创建微信APP支付订单
     *
     * @param payOrderVO 拉起APP支付必要的参数信息
     * @return 创建结果
     */
    public com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse createAppPayOrder(CcWxPayOrderVO payOrderVO) {
        com.wechat.pay.java.service.payments.app.model.PrepayRequest prepayRequest = orderBaseToAppRequestConverter(payOrderVO);
        return appServiceExtension.prepayWithRequestPayment(prepayRequest);
    }

    /**
     * 创建微信支付订单
     *
     * @return 创建结果
     */
    public PrepayWithRequestPaymentResponse createPayOrder(CcWxPayOrderVO payOrderVO) {
        PrepayRequest prepayRequest = this.orderBaseToRequestConverter(payOrderVO);
        return this.jsApiService.prepayWithRequestPayment(prepayRequest);
    }

    /**
     * 微信进行退款
     *
     * @param refundOrderVO 退款必要的参数
     * @return 退款结果
     */
    public Refund payOrderRefund(CcRefundOrderVO refundOrderVO) {
        CreateRequest createRequest = this.refundOrderVoToRequestConverter(refundOrderVO);
        return this.refundService.create(createRequest);
    }

    /**
     * 将退款的VO类转化成请求
     *
     * @param refundOrderVO 退款传递的参数信息
     * @return 创建出来的结果
     */
    private CreateRequest refundOrderVoToRequestConverter(CcRefundOrderVO refundOrderVO) {
        String orderNo = refundOrderVO.getOrderNo();
        BigDecimal realAmount = refundOrderVO.getPayAmount();
        CreateRequest request = new CreateRequest();
        AmountReq amountReq = new AmountReq();
        amountReq.setRefund(realAmount.longValue());
        amountReq.setTotal(realAmount.longValue());
        amountReq.setCurrency("CNY");
        request.setAmount(amountReq);
        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
        request.setOutRefundNo(orderNo);
        request.setSubMchid(wxPayProperties.getSubMchid());
        return request;
    }


    /**
     * 订单微信支付请求转换器
     *
     * @param payOrderVO 订单基本信息
     * @return 转换后的微信PrepayRequest
     */
    private com.wechat.pay.java.service.payments.app.model.PrepayRequest orderBaseToAppRequestConverter(CcWxPayOrderVO payOrderVO) {
        com.wechat.pay.java.service.payments.app.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.app.model.PrepayRequest();
        com.wechat.pay.java.service.payments.app.model.Amount wxOrderAmount = new com.wechat.pay.java.service.payments.app.model.Amount();
        int orderAmountIntValue = payOrderVO.getPayAmount().multiply(new BigDecimal(100)).intValue();
        wxOrderAmount.setTotal(orderAmountIntValue);
        prepayRequest.setAmount(wxOrderAmount);
        prepayRequest.setDescription(payOrderVO.getOrderNo());
        prepayRequest.setAppid(wxPayProperties.getMiniAppId());
        prepayRequest.setMchid(wxPayProperties.getSubMchid());
        prepayRequest.setOutTradeNo(payOrderVO.getOrderNo());
        prepayRequest.setNotifyUrl(wxPayProperties.getNotifyUrl());
        return prepayRequest;
    }

    /**
     * 订单微信支付请求转换器
     *
     * @param payOrderVO 订单基本信息
     * @return 转换后的微信PrepayRequest
     */
    private PrepayRequest orderBaseToRequestConverter(CcWxPayOrderVO payOrderVO) {
        PrepayRequest prepayRequest = new PrepayRequest();
        Amount wxOrderAmount = new Amount();
        Payer payer = new Payer();
        payer.setOpenid(payOrderVO.getOpenId());
        int orderAmountIntValue = payOrderVO.getPayAmount().multiply(new BigDecimal(100)).intValue();
        wxOrderAmount.setTotal(orderAmountIntValue);
        prepayRequest.setAmount(wxOrderAmount);
        prepayRequest.setDescription(payOrderVO.getOrderNo());
        prepayRequest.setPayer(payer);
        prepayRequest.setAppid(wxPayProperties.getMiniAppId());
        prepayRequest.setMchid(wxPayProperties.getSubMchid());
        prepayRequest.setOutTradeNo(payOrderVO.getOrderNo());
        prepayRequest.setNotifyUrl(wxPayProperties.getNotifyUrl());
        return prepayRequest;
    }


    /**
     * 读取请求体内容
     *
     * @param request HTTP请求对象
     * @return 请求体内容
     * @throws IOException 读取异常
     */
    private String readRequestBody(HttpServletRequest request) throws IOException {
        try (BufferedReader in = request.getReader()) {
            return in.lines().collect(Collectors.joining());
        }
    }


    /**
     * 解密支付状态
     *
     * @param resource 支付资源
     * @return 解密后的支付状态
     * @throws GeneralSecurityException 解密异常
     */
    private JSONObject decryptResource(JSONObject resource) throws GeneralSecurityException, IOException {
        String ciphertext = resource.getString("ciphertext");
        String associatedData = resource.getString("associated_data");
        String nonce = resource.getString("nonce");
        return JSON.parseObject(decryptResource(associatedData.getBytes(), nonce.getBytes(), ciphertext));

    }


    static final int TAG_LENGTH_BIT = 128;

    /**
     * 微信密钥解密方法
     *
     * @param associatedData 相关数据
     * @param nonce          随机数
     * @param ciphertext     密文
     * @return 解密结果
     */
    public String decryptResource(byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(wxPayProperties.getApiV3Key().getBytes(), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
